import type { MacAddress } from 'hap-nodejs'

import type {
  AccessoryIdentifier,
  AccessoryName,
  AccessoryPlugin,
  AccessoryPluginConstructor,
  PlatformIdentifier,
  PlatformName,
  PlatformPlugin,
  PlatformPluginConstructor,
} from './api.js'
import type { BridgeConfiguration, BridgeOptions, HomebridgeConfig } from './bridgeService.js'
import type { Plugin } from './plugin.js'
import type { PluginManagerOptions } from './pluginManager.js'

import { existsSync, readFileSync } from 'node:fs'
import process from 'node:process'

import chalk from 'chalk'
import { AccessoryEventTypes, MDNSAdvertiser } from 'hap-nodejs'
import qrcode from 'qrcode-terminal'

import { HomebridgeAPI, InternalAPIEvent, PluginType } from './api.js'
import { BridgeService } from './bridgeService.js'
import { ChildBridgeService } from './childBridgeService.js'
import { ExternalPortService } from './externalPortService.js'
import { IpcIncomingEvent, IpcOutgoingEvent, IpcService } from './ipcService.js'
import { Logger } from './logger.js'
import { MatterConfigValidator, MatterServer } from './matter/index.js'
import { PlatformAccessory } from './platformAccessory.js'
import { PluginManager } from './pluginManager.js'
import { User } from './user.js'
import { validMacAddress } from './util/mac.js'

const log = Logger.internal

export interface HomebridgeOptions {
  keepOrphanedCachedAccessories?: boolean
  hideQRCode?: boolean
  insecureAccess?: boolean
  customPluginPath?: string
  noLogTimestamps?: boolean
  debugModeEnabled?: boolean
  forceColourLogging?: boolean
  customStoragePath?: string
  strictPluginResolution?: boolean
}

// eslint-disable-next-line no-restricted-syntax
export const enum ServerStatus {
  /**
   * When the server is starting up
   */
  PENDING = 'pending',

  /**
   * When the server is online and has published the main bridge
   */
  OK = 'ok',

  /**
   * When the server is shutting down
   */
  DOWN = 'down',
}

export class Server {
  private readonly api: HomebridgeAPI
  private readonly pluginManager: PluginManager
  private readonly bridgeService: BridgeService
  private readonly ipcService: IpcService
  private readonly externalPortService: ExternalPortService

  private readonly config: HomebridgeConfig

  // used to keep track of child bridges
  // Key is HAP username (MAC address)
  private readonly childBridges: Map<string, ChildBridgeService> = new Map()

  // Matter server instance for main bridge (if enabled)
  private matterServer?: MatterServer

  // Cache for Matter bridge identifier -> ChildBridgeService lookups
  private readonly matterBridgeCache: Map<string, ChildBridgeService> = new Map()

  // current server status
  private serverStatus: ServerStatus = ServerStatus.PENDING

  constructor(
    private options: HomebridgeOptions = {},
  ) {
    this.config = Server.loadConfig()

    // object we feed to Plugins and BridgeService
    this.api = new HomebridgeAPI()
    this.ipcService = new IpcService()
    this.externalPortService = new ExternalPortService(this.config.ports)

    // set status to pending
    this.setServerStatus(ServerStatus.PENDING)

    // create new plugin manager
    const pluginManagerOptions: PluginManagerOptions = {
      activePlugins: this.config.plugins,
      disabledPlugins: this.config.disabledPlugins,
      customPluginPath: options.customPluginPath,
      strictPluginResolution: options.strictPluginResolution,
    }
    this.pluginManager = new PluginManager(this.api, pluginManagerOptions)

    // create new bridge service
    const bridgeConfig: BridgeOptions = {
      cachedAccessoriesDir: User.cachedAccessoryPath(),
      cachedAccessoriesItemName: 'cachedAccessories',
    }

    // shallow copy the homebridge options to the bridge options object
    Object.assign(bridgeConfig, this.options)

    this.bridgeService = new BridgeService(
      this.api,
      this.pluginManager,
      this.externalPortService,
      bridgeConfig,
      this.config.bridge,
    )

    // Handle platform accessory registration
    this.api.on(InternalAPIEvent.REGISTER_PLATFORM_ACCESSORIES, this.handleRegisterPlatformAccessories.bind(this))
    this.api.on(InternalAPIEvent.UNREGISTER_PLATFORM_ACCESSORIES, this.handleUnregisterPlatformAccessories.bind(this))

    // Handle external accessories (cameras, etc.)
    this.api.on(InternalAPIEvent.PUBLISH_EXTERNAL_ACCESSORIES, this.handlePublishExternalAccessories.bind(this))

    // Handle Matter accessory registration
    this.api.on(InternalAPIEvent.REGISTER_MATTER_ACCESSORY, this.handleRegisterMatterAccessory.bind(this))
    this.api.on(InternalAPIEvent.UNREGISTER_MATTER_ACCESSORY, this.handleUnregisterMatterAccessory.bind(this))
    this.api.on(InternalAPIEvent.UPDATE_MATTER_ACCESSORY_STATE, this.handleUpdateMatterAccessoryState.bind(this))

    // watch bridge events to check when server is online
    this.bridgeService.bridge.on(AccessoryEventTypes.ADVERTISED, () => {
      this.setServerStatus(ServerStatus.OK)
    })

    // watch for the paired event to update the server status
    this.bridgeService.bridge.on(AccessoryEventTypes.PAIRED, () => {
      this.setServerStatus(this.serverStatus)
    })

    // watch for the unpaired event to update the server status
    this.bridgeService.bridge.on(AccessoryEventTypes.UNPAIRED, () => {
      this.setServerStatus(this.serverStatus)
    })
  }

  /**
   * Set the current server status and update parent via IPC
   * @param status
   */
  private setServerStatus(status: ServerStatus) {
    this.serverStatus = status
    this.ipcService.sendMessage(IpcOutgoingEvent.SERVER_STATUS_UPDATE, {
      type: 'hap' as const, // Main bridge is HAP
      status: this.serverStatus,
      paired: this.bridgeService?.bridge?._accessoryInfo?.paired() ?? null,
      setupUri: this.bridgeService?.bridge?.setupURI() ?? null,
      name: this.bridgeService?.bridge?.displayName || this.config.bridge.name,
      username: this.config.bridge.username,
      pin: this.config.bridge.pin,
    })
  }

  public async start(): Promise<void> {
    if (this.config.bridge.disableIpc !== true) {
      this.initializeIpcEventHandlers()
    }

    const promises: Promise<void>[] = []

    // load the cached accessories
    await this.bridgeService.loadCachedPlatformAccessoriesFromDisk()

    // initialize plugins
    await this.pluginManager.initializeInstalledPlugins()

    // Initialize Matter server for main bridge if enabled
    await this.initializeMatterServer()

    if (this.config.platforms.length > 0) {
      promises.push(...this.loadPlatforms())
    }
    if (this.config.accessories.length > 0) {
      this.loadAccessories()
    }

    // start child HAP bridges
    for (const childBridge of this.childBridges.values()) {
      childBridge.start()
    }

    // restore cached accessories
    this.bridgeService.restoreCachedPlatformAccessories()

    this.api.signalFinished()

    // wait for all platforms to publish their accessories before we publish the bridge
    await Promise.all(promises)
      .then(() => this.publishBridge())
  }

  /**
   * Initialize Matter server for main bridge if enabled
   */
  private async initializeMatterServer(): Promise<void> {
    // Check if main bridge has matter configuration
    if (!this.config.bridge.matter) {
      return
    }

    // Declare matterPort outside try block so it's accessible in catch
    let matterPort: number | undefined

    try {
      log.info('Initializing Matter server for main bridge...')

      // Allocate port from pool if not explicitly configured
      matterPort = this.config.bridge.matter.port
      if (!matterPort) {
        matterPort = await this.externalPortService.requestPort(`${this.config.bridge.username}:MATTER` as MacAddress)
        if (!matterPort) {
          matterPort = 5540 // Default Matter port
          log.warn('No port available from pool for main Matter bridge, using default port 5540')
        } else {
          log.info(`Allocated port ${matterPort} from pool for main Matter bridge`)
        }
      }

      // Create Matter server instance with config inheritance from main bridge
      this.matterServer = new MatterServer({
        storagePath: User.matterPath(),
        port: matterPort,
        uniqueId: 'main-bridge',
        manufacturer: this.config.bridge.manufacturer,
        model: this.config.bridge.model,
        firmwareRevision: this.config.bridge.firmwareRevision,
        serialNumber: this.config.bridge.serialNumber,
        debugModeEnabled: this.options.debugModeEnabled,
      })

      // Start the Matter server
      await this.matterServer.start()

      log.info('Matter server initialized for main bridge')

      // Inform the API that Matter is enabled
      this.api._setMatterEnabled(true)

      // Set the Matter server reference for API methods like getAccessoryState
      this.api._setMatterServer(this.matterServer)

      // Send Matter status update to notify UI that Matter server is ready
      if (this.config.bridge.disableIpc !== true) {
        this.sendMainBridgeMatterStatusUpdate()
      }
    } catch (error: any) {
      log.error('Failed to initialize Matter server for main bridge:', error)

      // Provide user-friendly guidance for common errors
      if (error.message && error.message.includes('corrupted')) {
        log.error('')
        log.error('╔════════════════════════════════════════════════════════════════════════════╗')
        log.error('║  MATTER STORAGE CORRUPTED                                                  ║')
        log.error('╠════════════════════════════════════════════════════════════════════════════╣')
        log.error('║  Your Matter storage has become corrupted. This can happen when:          ║')
        log.error('║  • Matter.js library version changes                                       ║')
        log.error('║  • Storage format upgrades occur                                           ║')
        log.error('║  • Incomplete writes during shutdown                                       ║')
        log.error('║                                                                            ║')
        log.error('║  To fix this, delete the corrupted storage directory:                     ║')
        log.error('║  rm -rf ~/.homebridge/matter/main-bridge                                   ║')
        log.error('║                                                                            ║')
        log.error('║  Note: You will need to re-pair your Matter devices after deletion.       ║')
        log.error('╚════════════════════════════════════════════════════════════════════════════╝')
        log.error('')
      } else if (error.code === 'EADDRINUSE' || (error.message && error.message.includes('address already in use'))) {
        log.error('')
        log.error('╔════════════════════════════════════════════════════════════════════════════╗')
        log.error('║  MATTER PORT ALREADY IN USE                                                ║')
        log.error('╠════════════════════════════════════════════════════════════════════════════╣')
        log.error(`║  Port ${matterPort} is already in use by another application.                    ║`)
        log.error('║                                                                            ║')
        log.error('║  To fix this:                                                              ║')
        log.error('║  1. Stop the application using this port, or                              ║')
        log.error('║  2. Configure a different port in your config.json:                       ║')
        log.error('║     "bridge": {                                                            ║')
        log.error('║       "matter": {                                                          ║')
        log.error('║         "port": <different-port>                                           ║')
        log.error('║       }                                                                    ║')
        log.error('║     }                                                                      ║')
        log.error('╚════════════════════════════════════════════════════════════════════════════╝')
        log.error('')
      }
    }
  }

  /**
   * Send Matter status update for main bridge
   */
  private sendMainBridgeMatterStatusUpdate(): void {
    if (!this.matterServer || !this.matterServer.isServerRunning()) {
      return
    }

    const commissioningInfo = this.matterServer.getCommissioningInfo()

    // Transform property names to match UI expectations
    const statusUpdate = {
      type: 'matter' as const,
      status: 'ok' as const,
      port: this.config.bridge.matter?.port || 5540,
      setupUri: commissioningInfo.qrCode, // Map qrCode -> setupUri for UI
      pin: commissioningInfo.manualPairingCode, // Map manualPairingCode -> pin for UI
      serialNumber: commissioningInfo.serialNumber,
      passcode: commissioningInfo.passcode,
      discriminator: commissioningInfo.discriminator,
      name: `${this.config.bridge.name} (Matter)`,
      plugin: 'main-bridge',
      identifier: 'main-bridge',
      deviceCount: this.matterServer.getAccessories().length,
      commissioned: commissioningInfo.commissioned,
    }

    this.ipcService.sendMessage(IpcOutgoingEvent.MATTER_BRIDGE_STATUS_UPDATE, statusUpdate)
  }

  public async teardown(): Promise<void> {
    this.bridgeService.teardown()

    // Stop main Matter server if running
    if (this.matterServer) {
      try {
        await this.matterServer.stop()
      } catch (error) {
        log.error('Failed to stop Matter server:', error)
      }
    }

    // Child bridge Matter servers are stopped by their own forked processes

    this.setServerStatus(ServerStatus.DOWN)
  }

  private publishBridge(): void {
    this.bridgeService.publishBridge()
    this.printSetupInfo(this.config.bridge.pin)
  }

  private handlePublishExternalAccessories(accessories: PlatformAccessory[]): void {
    log.info(`Publishing ${accessories.length} external accessories`)
    // External accessories are published via HAP
    // Plugins should use api.matter to register Matter accessories explicitly
  }

  private handleRegisterPlatformAccessories(accessories: PlatformAccessory[]): void {
    // Route to HAP bridge
    this.bridgeService.handleRegisterPlatformAccessories(accessories)
  }

  private handleUnregisterPlatformAccessories(accessories: PlatformAccessory[]): void {
    // Route to HAP bridge
    this.bridgeService.handleUnregisterPlatformAccessories(accessories)
  }

  private handleRegisterMatterAccessory(accessory: any): void {
    if (!this.matterServer) {
      log.warn('Cannot register Matter accessory - Matter server is not running')
      return
    }
    this.matterServer.registerAccessory(accessory)
  }

  private handleUnregisterMatterAccessory(uuid: string): void {
    if (!this.matterServer) {
      log.warn('Cannot unregister Matter accessory - Matter server is not running')
      return
    }
    this.matterServer.unregisterAccessory(uuid)
  }

  private handleUpdateMatterAccessoryState(uuid: string, cluster: string, attributes: Record<string, any>): void {
    if (!this.matterServer) {
      log.warn('Cannot update Matter accessory state - Matter server is not running')
      return
    }
    this.matterServer.updateAccessoryState(uuid, cluster, attributes)
  }

  private static loadConfig(): HomebridgeConfig {
    // Look for the configuration file
    const configPath = User.configPath()

    const defaultBridge: BridgeConfiguration = {
      name: 'Homebridge',
      username: 'CC:22:3D:E3:CE:30',
      pin: '031-45-154',
    }

    if (!existsSync(configPath)) {
      log.warn('config.json (%s) not found.', configPath)
      return { // return a default configuration
        bridge: defaultBridge,
        accessories: [],
        platforms: [],
      }
    }

    let config: Partial<HomebridgeConfig>
    try {
      config = JSON.parse(readFileSync(configPath, { encoding: 'utf8' }))
    } catch (error: any) {
      log.error('There was a problem reading your config.json file.')
      log.error('Please try pasting your config.json file here to validate it: https://jsonlint.com')
      log.error('')
      throw error
    }

    if (config.ports !== undefined) {
      if (config.ports.start && config.ports.end) {
        if (config.ports.start > config.ports.end) {
          log.error('Invalid port pool configuration. End should be greater than or equal to start.')
          config.ports = undefined
        }
      } else {
        log.error('Invalid configuration for \'ports\'. Missing \'start\' and \'end\' properties! Ignoring it!')
        config.ports = undefined
      }
    }

    const bridge: BridgeConfiguration = config.bridge || defaultBridge
    bridge.name = bridge.name || defaultBridge.name
    bridge.username = bridge.username || defaultBridge.username
    bridge.pin = bridge.pin || defaultBridge.pin
    config.bridge = bridge

    const username = config.bridge.username
    if (!validMacAddress(username)) {
      throw new Error(`Not a valid username: ${username}. Must be 6 pairs of colon-separated hexadecimal chars (A-F 0-9), like a MAC address.`)
    }

    config.accessories = config.accessories || []
    config.platforms = config.platforms || []

    if (!Array.isArray(config.accessories)) {
      log.error('Value provided for accessories must be an array[]')
      config.accessories = []
    }

    if (!Array.isArray(config.platforms)) {
      log.error('Value provided for platforms must be an array[]')
      config.platforms = []
    }

    log.info('Loaded config.json with %s accessories and %s platforms.', config.accessories.length, config.platforms.length)

    // Validate Matter configuration for port conflicts
    if (config.bridge.matter || config.platforms.some((p: any) => p._bridge?.matter) || config.accessories.some((a: any) => a._bridge?.matter)) {
      // Validate main bridge Matter config
      if (config.bridge.matter) {
        const validation = MatterConfigValidator.validate(config.bridge.matter)
        if (!validation.isValid) {
          log.error('Main bridge Matter configuration is invalid. Matter will not be enabled for the main bridge.')
          delete config.bridge.matter
        }
      }

      // Validate all child bridge Matter configs and check for port conflicts
      const childMatterValidation = MatterConfigValidator.validateAllChildMatterConfigs(
        config.platforms as any[],
        config.accessories as any[],
      )

      if (!childMatterValidation.isValid) {
        log.error('Some child bridge Matter configurations are invalid. Check the errors above.')
      }

      // Additionally, check for conflicts between main bridge Matter port and child bridge ports
      if (config.bridge.matter?.port) {
        const mainMatterPort = config.bridge.matter.port
        const childMatterPorts: number[] = []

        for (const platform of config.platforms as any[]) {
          if (platform._bridge?.matter?.port) {
            childMatterPorts.push(platform._bridge.matter.port)
          }
        }

        for (const accessory of config.accessories as any[]) {
          if (accessory._bridge?.matter?.port) {
            childMatterPorts.push(accessory._bridge.matter.port)
          }
        }

        if (childMatterPorts.includes(mainMatterPort)) {
          log.error(`Main bridge Matter port ${mainMatterPort} conflicts with a child bridge Matter port. Please use unique ports.`)
        }

        // Check for conflict with main bridge HAP port
        if (config.bridge.port && Math.abs(config.bridge.port - mainMatterPort) < 10) {
          log.warn(`Main bridge HAP port ${config.bridge.port} and Matter port ${mainMatterPort} are very close. Consider spacing them further apart.`)
        }
      }
    }

    if (config.bridge.advertiser) {
      if (![
        MDNSAdvertiser.BONJOUR,
        MDNSAdvertiser.CIAO,
        MDNSAdvertiser.AVAHI,
        MDNSAdvertiser.RESOLVED,
      ].includes(config.bridge.advertiser)) {
        config.bridge.advertiser = undefined
        log.error('Value provided in bridge.advertiser is not valid, reverting to platform default.')
      }
    } else {
      config.bridge.advertiser = undefined
    }

    return config as HomebridgeConfig
  }

  private loadAccessories(): void {
    log.info(`Loading ${this.config.accessories.length} accessories...`)

    this.config.accessories.forEach((accessoryConfig, index) => {
      if (!accessoryConfig.accessory) {
        log.warn('Your config.json contains an illegal accessory configuration object at position %d. '
          + 'Missing property \'accessory\'. Skipping entry...', index + 1) // we rather count from 1 for the normal people?
        return
      }

      const accessoryIdentifier: AccessoryName | AccessoryIdentifier = accessoryConfig.accessory
      const displayName = accessoryConfig.name
      if (!displayName) {
        log.warn('Could not load accessory %s at position %d as it is missing the required \'name\' property!', accessoryIdentifier, index + 1)
        return
      }

      let plugin: Plugin
      let constructor: AccessoryPluginConstructor

      try {
        plugin = this.pluginManager.getPluginForAccessory(accessoryIdentifier)
      } catch (error: any) {
        log.error(error.message)
        return
      }

      // check the plugin is not disabled
      if (plugin.disabled) {
        log.warn(`Ignoring config for the accessory "${accessoryIdentifier}" in your config.json as the plugin "${plugin.getPluginIdentifier()}" has been disabled.`)
        return
      }

      try {
        constructor = plugin.getAccessoryConstructor(accessoryIdentifier)
      } catch (error: any) {
        log.error(`Error loading the accessory "${accessoryIdentifier}" requested in your config.json at position ${index + 1} - this is likely an issue with the "${plugin.getPluginIdentifier()}" plugin.`)
        log.error(error) // error message contains more information and full stack trace
        return
      }

      const logger = Logger.withPrefix(displayName)
      logger('Initializing %s accessory...', accessoryIdentifier)

      if (accessoryConfig._bridge) {
        // ensure the username is always uppercase
        accessoryConfig._bridge.username = accessoryConfig._bridge.username.toUpperCase()

        try {
          this.validateChildBridgeConfig(PluginType.ACCESSORY, accessoryIdentifier, accessoryConfig._bridge)
        } catch (error: any) {
          log.error(error.message)
          return
        }

        let childBridge: ChildBridgeService

        if (this.childBridges.has(accessoryConfig._bridge.username)) {
          childBridge = this.childBridges.get(accessoryConfig._bridge.username)!
          logger(`Adding to existing child bridge ${accessoryConfig._bridge.username}`)
        } else {
          logger(`Initializing child bridge ${accessoryConfig._bridge.username}`)
          childBridge = new ChildBridgeService(
            PluginType.ACCESSORY,
            accessoryIdentifier,
            plugin,
            accessoryConfig._bridge,
            this.config,
            this.options,
            this.api,
            this.ipcService,
            this.externalPortService,
          )

          this.childBridges.set(accessoryConfig._bridge.username, childBridge)
        }

        // add config to child bridge service
        childBridge.addConfig(accessoryConfig)

        return
      }

      const accessoryInstance: AccessoryPlugin = new constructor(logger, accessoryConfig, this.api)

      // pass accessoryIdentifier for UUID generation, and optional parameter uuid_base which can be used instead of displayName for UUID generation
      const accessory = this.bridgeService.createHAPAccessory(plugin, accessoryInstance, displayName, accessoryIdentifier, accessoryConfig.uuid_base)

      if (accessory) {
        try {
          this.bridgeService.bridge.addBridgedAccessory(accessory)
        } catch (error: any) {
          logger.error(`Error loading the accessory "${accessoryIdentifier}" from "${plugin.getPluginIdentifier()}" requested in your config.json:`, error.message)
        }
      } else {
        logger.info('Accessory %s returned empty set of services; not adding it to the bridge.', accessoryIdentifier)
      }
    })
  }

  private loadPlatforms(): Promise<void>[] {
    log.info(`Loading ${this.config.platforms.length} platforms...`)

    const promises: Promise<void>[] = []
    this.config.platforms.forEach((platformConfig, index) => {
      if (!platformConfig.platform) {
        log.warn('Your config.json contains an illegal platform configuration object at position %d. '
          + 'Missing property \'platform\'. Skipping entry...', index + 1) // we rather count from 1 for the normal people?
        return
      }

      const platformIdentifier: PlatformName | PlatformIdentifier = platformConfig.platform
      const displayName = platformConfig.name || platformIdentifier

      let plugin: Plugin
      let constructor: PlatformPluginConstructor

      // do not load homebridge-config-ui-x when running in service mode
      if (platformIdentifier === 'config' && process.env.UIX_SERVICE_MODE === '1') {
        return
      }

      try {
        plugin = this.pluginManager.getPluginForPlatform(platformIdentifier)
      } catch (error: any) {
        log.error(error.message)
        return
      }

      // check the plugin is not disabled
      if (plugin.disabled) {
        log.warn(`Ignoring config for the platform "${platformIdentifier}" in your config.json as the plugin "${plugin.getPluginIdentifier()}" has been disabled.`)
        return
      }

      try {
        constructor = plugin.getPlatformConstructor(platformIdentifier)
      } catch (error: any) {
        log.error(`Error loading the platform "${platformIdentifier}" requested in your config.json at position ${index + 1} - this is likely an issue with the "${plugin.getPluginIdentifier()}" plugin.`)
        log.error(error) // error message contains more information and full stack trace
        return
      }

      const logger = Logger.withPrefix(displayName)
      logger('Initializing %s platform...', platformIdentifier)

      if (platformConfig._bridge) {
        // ensure the username is always uppercase
        platformConfig._bridge.username = platformConfig._bridge.username.toUpperCase()

        try {
          this.validateChildBridgeConfig(PluginType.PLATFORM, platformIdentifier, platformConfig._bridge)
        } catch (error: any) {
          log.error(error.message)
          return
        }

        logger(`Initializing child bridge ${platformConfig._bridge.username}`)
        const childBridge = new ChildBridgeService(
          PluginType.PLATFORM,
          platformIdentifier,
          plugin,
          platformConfig._bridge,
          this.config,
          this.options,
          this.api,
          this.ipcService,
          this.externalPortService,
        )

        this.childBridges.set(platformConfig._bridge.username, childBridge)

        // add config to child bridge service
        childBridge.addConfig(platformConfig)
        return
      }

      const platform: PlatformPlugin = new constructor(logger, platformConfig, this.api)

      if (HomebridgeAPI.isDynamicPlatformPlugin(platform)) {
        plugin.assignDynamicPlatform(platformIdentifier, platform)
      } else if (HomebridgeAPI.isStaticPlatformPlugin(platform)) { // Plugin 1.0, load accessories
        promises.push(this.bridgeService.loadPlatformAccessories(plugin, platform, platformIdentifier, logger))
      } else {
        // otherwise it's a IndependentPlatformPlugin which doesn't expose any methods at all.
        // We just call the constructor and let it be enabled.
      }
    })

    return promises
  }

  /**
   * Validate an external bridge config
   */
  private validateChildBridgeConfig(type: PluginType, identifier: string, bridgeConfig: BridgeConfiguration): void {
    // All child bridges require username
    if (!bridgeConfig.username) {
      throw new Error(
        `Error loading the ${type} "${identifier}" requested in your config.json - `
        + 'Missing required field "_bridge.username".',
      )
    }

    if (!validMacAddress(bridgeConfig.username)) {
      throw new Error(
        `Error loading the ${type} "${identifier}" requested in your config.json - `
        + `not a valid username in _bridge.username: "${bridgeConfig.username}". Must be 6 pairs of colon-separated hexadecimal chars (A-F 0-9), like a MAC address.`,
      )
    }

    if (this.childBridges.has(bridgeConfig.username)) {
      const childBridge = this.childBridges.get(bridgeConfig.username)
      if (type === PluginType.PLATFORM) {
        // only a single platform can exist on one child bridge
        throw new Error(
          `Error loading the ${type} "${identifier}" requested in your config.json - `
          + `Duplicate username found in _bridge.username: "${bridgeConfig.username}". Each platform child bridge must have it's own unique username.`,
        )
      } else if (childBridge?.identifier !== identifier) {
        // only accessories of the same type can be added to the same child bridge
        throw new Error(
          `Error loading the ${type} "${identifier}" requested in your config.json - `
          + `Duplicate username found in _bridge.username: "${bridgeConfig.username}". You can only group accessories of the same type in a child bridge.`,
        )
      }
    }

    if (bridgeConfig.username === this.config.bridge.username.toUpperCase()) {
      throw new Error(
        `Error loading the ${type} "${identifier}" requested in your config.json - `
        + `Username found in _bridge.username: "${bridgeConfig.username}" is the same as the main bridge. Each child bridge platform/accessory must have it's own unique username.`,
      )
    }
  }

  /**
   * Takes care of the IPC Events sent to Homebridge
   */
  private initializeIpcEventHandlers() {
    // start ipc service
    this.ipcService.start()

    // handle restart child bridge event
    this.ipcService.on(IpcIncomingEvent.RESTART_CHILD_BRIDGE, (username) => {
      // noinspection SuspiciousTypeOfGuard
      if (typeof username === 'string') {
        const childBridge = this.childBridges.get(username.toUpperCase())
        childBridge?.restartChildBridge()
      }
    })

    // handle stop child bridge event
    this.ipcService.on(IpcIncomingEvent.STOP_CHILD_BRIDGE, (username) => {
      // noinspection SuspiciousTypeOfGuard
      if (typeof username === 'string') {
        const childBridge = this.childBridges.get(username.toUpperCase())
        childBridge?.stopChildBridge()
      }
    })

    // handle start child bridge event
    this.ipcService.on(IpcIncomingEvent.START_CHILD_BRIDGE, (username) => {
      // noinspection SuspiciousTypeOfGuard
      if (typeof username === 'string') {
        const childBridge = this.childBridges.get(username.toUpperCase())
        childBridge?.startChildBridge()
      }
    })

    this.ipcService.on(IpcIncomingEvent.CHILD_BRIDGE_METADATA_REQUEST, () => {
      const childBridgeMetadata = Array.from(this.childBridges.values()).map(x => x.getMetadata())
      this.ipcService.sendMessage(IpcOutgoingEvent.CHILD_BRIDGE_METADATA_RESPONSE, childBridgeMetadata)
    })

    // Matter bridge IPC handlers
    // Main bridge Matter server runs in this process alongside HAP
    // Child bridge Matter servers run in forked child processes (see childBridgeFork.ts)

    this.ipcService.on(IpcIncomingEvent.RESTART_MATTER_BRIDGE, (matterBridgeId: string) => {
      if (matterBridgeId === 'main-bridge') {
        // Main Matter bridge runs in the main process, so restart the entire Homebridge instance
        log.info('Restarting Homebridge (Matter bridge restart requested)...')
        process.kill(process.pid, 'SIGTERM')
      } else {
        // Handle child bridge using cache for efficient lookup
        const childBridge = this.matterBridgeCache.get(matterBridgeId)
        if (childBridge) {
          childBridge.restartChildBridge()
        } else {
          log.warn(`Child bridge ${matterBridgeId} not found for Matter restart`)
        }
      }
    })

    this.ipcService.on(IpcIncomingEvent.STOP_MATTER_BRIDGE, (matterBridgeId: string) => {
      if (matterBridgeId === 'main-bridge') {
        log.warn('Stop requested for main Matter bridge - this is not supported')
        log.info('The main Matter bridge runs alongside the HAP bridge and cannot be stopped independently')
      } else {
        // Handle child bridge using cache for efficient lookup
        const childBridge = this.matterBridgeCache.get(matterBridgeId)
        if (childBridge) {
          childBridge.stopChildBridge()
        } else {
          log.warn(`Child bridge ${matterBridgeId} not found for Matter stop`)
        }
      }
    })

    this.ipcService.on(IpcIncomingEvent.START_MATTER_BRIDGE, (matterBridgeId: string) => {
      if (matterBridgeId === 'main-bridge') {
        log.warn('Start requested for main Matter bridge - this is not supported')
        log.info('The main Matter bridge starts automatically with Homebridge')
      } else {
        // Handle child bridge using cache for efficient lookup
        const childBridge = this.matterBridgeCache.get(matterBridgeId)
        if (childBridge) {
          childBridge.startChildBridge()
        } else {
          log.warn(`Child bridge ${matterBridgeId} not found for Matter start`)
        }
      }
    })

    this.ipcService.on(IpcIncomingEvent.MATTER_BRIDGE_METADATA_REQUEST, () => {
      // Return all Matter bridge metadata (main + child bridges)
      interface MatterBridgeMetadataResponse {
        type: 'matter'
        status: 'ok' | 'pending'
        port?: number
        setupUri?: string
        pin?: string
        serialNumber?: string
        name: string
        plugin: string
        identifier: string
        deviceCount?: number
        commissioned: boolean
        matterEnabled: boolean
        username?: string
      }

      /**
       * Sanitize Matter bridge identifier to match MatterServer filesystem sanitization
       * Must match the logic in MatterServer: lowercase, replace non-alphanumeric with dashes
       */
      const sanitizeMatterId = (id: string): string => {
        return id
          .replace(/[^a-z0-9]/gi, '-')
          .replace(/-+/g, '-')
          .replace(/^-|-$/g, '')
          .toLowerCase()
      }

      const matterBridges: MatterBridgeMetadataResponse[] = []

      // Add main bridge Matter metadata if enabled
      if (this.matterServer && this.matterServer.isServerRunning()) {
        const commissioningInfo = this.matterServer.getCommissioningInfo()
        matterBridges.push({
          type: 'matter',
          status: 'ok',
          port: this.config.bridge.matter?.port || 5540,
          setupUri: commissioningInfo.qrCode,
          pin: commissioningInfo.manualPairingCode,
          serialNumber: commissioningInfo.serialNumber,
          name: this.config.bridge.name,
          plugin: 'main-bridge',
          identifier: 'main-bridge',
          deviceCount: this.matterServer.getAccessories().length,
          commissioned: commissioningInfo.commissioned,
          matterEnabled: true,
        })
      }

      // Add child bridge Matter metadata
      for (const childBridge of this.childBridges.values()) {
        const matterInfo = childBridge.getCommissioningInfo()
        const matterConfig = childBridge.getMatterConfig()
        if (matterInfo && matterConfig) {
          const metadata = childBridge.getMetadata()
          // Sanitize the Matter identifier to match filesystem (lowercase, dashes)
          const childIdentifier = sanitizeMatterId(metadata.matterIdentifier!)

          // Populate cache for efficient lookups
          this.matterBridgeCache.set(childIdentifier, childBridge)

          matterBridges.push({
            type: 'matter',
            status: metadata.status === 'ok' ? 'ok' : 'pending',
            port: matterConfig.port,
            setupUri: matterInfo.qrCode,
            pin: matterInfo.manualPairingCode,
            serialNumber: matterInfo.serialNumber,
            name: metadata.name,
            plugin: metadata.plugin,
            identifier: childIdentifier,
            commissioned: matterInfo.commissioned,
            username: metadata.username,
            matterEnabled: true,
          })
        }
      }

      this.ipcService.sendMessage(
        IpcOutgoingEvent.MATTER_BRIDGE_METADATA_RESPONSE,
        matterBridges,
      )
    })

    this.ipcService.on(IpcIncomingEvent.MATTER_COMMISSIONING_INFO_REQUEST, (matterBridgeId: string) => {
      let rawInfo: any = { commissioned: false }

      // Handle main bridge
      if (matterBridgeId === 'main-bridge') {
        if (this.matterServer && this.matterServer.isServerRunning()) {
          rawInfo = this.matterServer.getCommissioningInfo()
        } else {
          log.debug(`[Matter] Server not available when commissioning info requested for: ${matterBridgeId}`)
        }
      } else {
        // Handle child bridge using plugin-identifier format (e.g., homebridge-virtual-accessories-VirtualAccessoriesForHomebridge)
        // Use cache for efficient O(1) lookup
        const childBridge = this.matterBridgeCache.get(matterBridgeId)
        if (childBridge) {
          rawInfo = childBridge.getCommissioningInfo() || { commissioned: false }
        } else {
          log.debug(`[Matter] Child bridge ${matterBridgeId} not found when commissioning info requested`)
        }
      }

      // Transform property names to match MatterCommissioningInfo interface
      const commissioningInfo = {
        commissioned: rawInfo.commissioned || false,
        pin: rawInfo.manualPairingCode, // Map manualPairingCode -> pin
        setupUri: rawInfo.qrCode, // Map qrCode -> setupUri
        serialNumber: rawInfo.serialNumber,
      }

      this.ipcService.sendMessage(
        IpcOutgoingEvent.MATTER_COMMISSIONING_INFO_RESPONSE,
        commissioningInfo,
      )
    })
  }

  private printSetupInfo(pin: string): void {
    /* eslint-disable no-console */
    console.log('Setup Payload:')
    console.log(this.bridgeService.bridge.setupURI())

    if (!this.options.hideQRCode) {
      console.log('Scan this code with your HomeKit app on your iOS device to pair with Homebridge:')
      qrcode.setErrorLevel('M') // HAP specifies level M or higher for ECC
      qrcode.generate(this.bridgeService.bridge.setupURI())
      console.log('Or enter this code with your HomeKit app on your iOS device to pair with Homebridge:')
    } else {
      console.log('Enter this code with your HomeKit app on your iOS device to pair with Homebridge:')
    }

    console.log(chalk.black.bgWhite('                       '))
    console.log(chalk.black.bgWhite('    ┌────────────┐     '))
    console.log(chalk.black.bgWhite(`    │ ${pin} │     `))
    console.log(chalk.black.bgWhite('    └────────────┘     '))
    console.log(chalk.black.bgWhite('                       '))
    /* eslint-enable no-console */
  }
}
